package tools;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class Derivative {
	
	/**
	 * Diese Funktion berechnet die X-Ableitung eines Helligkeitsbildes
	 * 
	 * @param src Eingabedaten in 0-255
	 * @param w width
	 * @param h height
	 * @param dst Ausgabedaten
	 * @return dst
	 */
	public static int[] dx(int[] src, int w, int h, int[] dst) {
		for (int i = 0; i<src.length; i++) {
			int x = i%w;
			int index = (x+1)<w?i+1:i;
			dst[i] = (src[i]-src[index]);
		}
		return dst;
	}
	public static double[] dx(double[] src, int w, int h, double[] dst) {
		for (int i = 0; i<src.length; i++) {
			int x = i%w;
			int index = (x+1)<w?i+1:i;
			dst[i] = (src[i]-src[index])/2.;
		}
		return dst;
	}
	/**
	 * Diese Funktion berechnet die Y-Ableitung eines Helligkeitbildes
	 * 
	 * @param src Eingabedaten in 0-255
	 * @param w	width
	 * @param h height
	 * @param dst Ausgabedaten
	 * @return dst
	 */
	public static int[] dy(int[] src, int w, int h, int[] dst) {
		for (int i = 0; i<src.length; i++) {
			int index = (i+w)<src.length?i+w:i;
			dst[i] = src[i]-src[index];
		}
		return dst;
	}
	public static double[] dy(double[] src, int w, int h, double[] dst) {
		for (int i = 0; i<src.length; i++) {
			int index = (i+1*w)<src.length?i+1*w:i;
			dst[i] = -(src[i]-src[index])/2.;
		}
		return dst;
	}
	public static int[] dt(int[] t0, int[] t1, int[] fillme) {
		for (int i = 0; i<t0.length; i++) {
			fillme[i] = t1[i]-t0[i];
		}
		return fillme;
	}
	public static double[] dt(double[] t0, double[] t1, double[] fillme) {
		for (int i = 0; i<t0.length; i++) {
			fillme[i] = (t1[i]-t0[i])/2.;
		}
		return fillme;
	}
	public static int[] prepareForShow(int[] derivative, int[] fillme) {
		for (int i = 0; i<derivative.length; i++) {
			fillme[i] = (derivative[i]>>1)+128;
		}
		return fillme;
	}
	
	/**
	 * Berechnet den Winkel der Ableitungen
	 * 
	 * @param dx	x derivatives
	 * @param dy	y derivatives
	 * @param fillme	array that is being returned
	 * @return angles ranging from -Math.PI to Math.PI;
	 **/
	public static double[] getAngles(int[] dx, int[] dy, double[] fillme) {
		for (int i = 0; i<dx.length; i++) {
			fillme[i] = Math.atan2(-dy[i],dx[i]);
		}
		return fillme;
	}
	
	public static int[] prepareAngles(double[] angles, int[] fillme) {
		for (int i = 0; i<angles.length; i++) {
			fillme[i] = (int)(255*(angles[i]+Math.PI)/(2*Math.PI));
		}
		return fillme;
	}
	public static double[] getLengths(int[] dx, int[] dy, double[] fillme) {
		for (int i = 0; i<dx.length; i++) {
			fillme[i] = Math.sqrt(dx[i]*dx[i]+dy[i]*dy[i]);
		}
		return fillme;
	}
	final static double normLength = Math.sqrt(2);
	
	public static int[] prepareLengths(double[] lengths, int[] fillme) {
		for (int i = 0; i<lengths.length; i++) {
			fillme[i] = (int)(lengths[i]/normLength);
		}
		return fillme;
	}
	public static void main(String[] args) throws Exception {
		JFrame ff = new JFrame();
		ff.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		final BufferedImage bi = ImageIO.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("res/kermit001.jpg"));
		final int w = bi.getWidth(), h = bi.getHeight();
		final JPanel panel = new JPanel();
		
		final BufferedImage dxI = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		final BufferedImage dyI = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		final BufferedImage angI = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		
		int[] pixels = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), new int[bi.getWidth()*bi.getHeight()], 0, bi.getWidth());
		int[] lum = ColorConversions.getFastLuminance(pixels, new int[bi.getWidth()*bi.getHeight()]);
		
//		double[] temp1 = new double[pixels.length], temp2 = new double[pixels.length];
//		for (int i = 0; i < temp2.length; i++) {
//			temp1[i] = lum[i];
//		}
//		double sigma = 5;
//		temp2 = Gauss.gaussX(temp1, w, h, sigma, temp2);
//		temp1 = Gauss.gaussY(temp2, w, h, sigma, temp1);
//		
//		for (int i = 0; i<lum.length; i++) {
//			lum[i] = (int)Math.round(temp1[i]);
//		}
//		pixels = Gauss.gaussX(grey, w, h, sigma, pixels);
//		grey = Gauss.gaussY(pixels, w, h, sigma, grey);
		
		
		int[] dx = Derivative.dx(lum, bi.getWidth(), bi.getHeight(), new int[bi.getWidth()*bi.getHeight()]);
		int[] dy = Derivative.dy(lum, bi.getWidth(), bi.getHeight(), new int[bi.getWidth()*bi.getHeight()]);
		
		double[] angles = Derivative.getAngles(dx, dy, new double[dx.length]);
		lum = Derivative.prepareForShow(dx, lum);
		pixels = ColorConversions.buildRGB(lum, lum, lum, pixels);
		dxI.setRGB(0, 0, w, h, pixels, 0, w);
		lum = Derivative.prepareForShow(dy, lum);
		pixels = ColorConversions.buildRGB(lum, lum, lum, pixels);
		dyI.setRGB(0, 0, w, h, pixels, 0, w);
		lum = Derivative.prepareAngles(angles, lum);
		pixels = ColorConversions.buildRGB(lum, lum, lum, pixels);
		angI.setRGB(0, 0, w, h, pixels, 0, w);
		double[] lengths = Derivative.getLengths(dx, dy, new double[dx.length]);
		lum = Derivative.prepareLengths(lengths, lum);
		double max = 0;
		for (int i = 0; i<lengths.length; i++) {
			max = Math.max(max, lum[i]);
		}
		System.out.println(max);
		pixels = ColorConversions.buildRGB(lum, lum, lum, pixels);
		bi.setRGB(0, 0, bi.getWidth(), bi.getHeight(), pixels, 0, bi.getWidth());
		{
			final AffineTransform at = new AffineTransform();
			final JComponent canvas = new JComponent() {
				{
					setPreferredSize(new Dimension(bi.getWidth(), bi.getHeight()));
				}
				@Override
				public void paint(Graphics g) {
					Graphics2D g2 = (Graphics2D)g;
					g2.transform(at);
					g2.drawImage(dxI, 0, 0, this);
					super.paint(g2);
				}
			};
			canvas.addMouseWheelListener(new MouseWheelListener() {
				
				@Override
				public void mouseWheelMoved(MouseWheelEvent e) {
					
					double scale = Math.pow(1.01, e.getWheelRotation());

					try {
						Point2D p = new Point2D.Double(e.getX(), e.getY()); 
						at.inverseTransform(p, p);
						at.concatenate(AffineTransform.getTranslateInstance(p.getX(), p.getY()));
						at.concatenate(AffineTransform.getScaleInstance(scale, scale));
						at.concatenate(AffineTransform.getTranslateInstance(-p.getX(), -p.getY()));
					}
					catch (NoninvertibleTransformException nte ) {
						nte.printStackTrace();
					}
					canvas.repaint();
				}
			});
			panel.add(canvas);

		}
		{
			final AffineTransform at = new AffineTransform();
			final JComponent canvas = new JComponent() {
				{
					setPreferredSize(new Dimension(bi.getWidth(), bi.getHeight()));
				}
				@Override
				public void paint(Graphics g) {
					Graphics2D g2 = (Graphics2D)g;
					g2.transform(at);
					g2.drawImage(dyI, 0, 0, this);
					super.paint(g2);
				}
			};
			canvas.addMouseWheelListener(new MouseWheelListener() {
				
				@Override
				public void mouseWheelMoved(MouseWheelEvent e) {
					
					double scale = Math.pow(1.01, e.getWheelRotation());

					try {
						Point2D p = new Point2D.Double(e.getX(), e.getY()); 
						at.inverseTransform(p, p);
						at.concatenate(AffineTransform.getTranslateInstance(p.getX(), p.getY()));
						at.concatenate(AffineTransform.getScaleInstance(scale, scale));
						at.concatenate(AffineTransform.getTranslateInstance(-p.getX(), -p.getY()));
					}
					catch (NoninvertibleTransformException nte ) {
						nte.printStackTrace();
					}
					canvas.repaint();
				}
			});
			panel.add(canvas);

		}
		{
			final AffineTransform at = new AffineTransform();
			final JComponent canvas = new JComponent() {
				{
					setPreferredSize(new Dimension(bi.getWidth(), bi.getHeight()));
				}
				@Override
				public void paint(Graphics g) {
					Graphics2D g2 = (Graphics2D)g;
					g2.transform(at);
					g2.drawImage(angI, 0, 0, this);
					super.paint(g2);
				}
			};
			canvas.addMouseWheelListener(new MouseWheelListener() {
				
				@Override
				public void mouseWheelMoved(MouseWheelEvent e) {
					
					double scale = Math.pow(1.01, e.getWheelRotation());

					try {
						Point2D p = new Point2D.Double(e.getX(), e.getY()); 
						at.inverseTransform(p, p);
						at.concatenate(AffineTransform.getTranslateInstance(p.getX(), p.getY()));
						at.concatenate(AffineTransform.getScaleInstance(scale, scale));
						at.concatenate(AffineTransform.getTranslateInstance(-p.getX(), -p.getY()));
					}
					catch (NoninvertibleTransformException nte ) {
						nte.printStackTrace();
					}
					canvas.repaint();
				}
			});
			panel.add(canvas);

		}
		{
			final AffineTransform at = new AffineTransform();
			final JComponent canvas = new JComponent() {
				{
					setPreferredSize(new Dimension(bi.getWidth(), bi.getHeight()));
				}
				@Override
				public void paint(Graphics g) {
					Graphics2D g2 = (Graphics2D)g;
					g2.transform(at);
					g2.drawImage(bi, 0, 0, this);
					
					super.paint(g2);
				}
			};
			canvas.addMouseWheelListener(new MouseWheelListener() {
				
				@Override
				public void mouseWheelMoved(MouseWheelEvent e) {
					
					double scale = Math.pow(1.01, e.getWheelRotation());

					try {
						Point2D p = new Point2D.Double(e.getX(), e.getY()); 
						at.inverseTransform(p, p);
						at.concatenate(AffineTransform.getTranslateInstance(p.getX(), p.getY()));
						at.concatenate(AffineTransform.getScaleInstance(scale, scale));
						at.concatenate(AffineTransform.getTranslateInstance(-p.getX(), -p.getY()));
					}
					catch (NoninvertibleTransformException nte ) {
						nte.printStackTrace();
					}
					canvas.repaint();
				}
			});
			panel.add(canvas);

		}
		ff.add(new JScrollPane(panel));
		ff.setBounds(100, 100, 800, 600);
//		ff.pack();
		ff.setVisible(true);
//		try {
//			ImageIO.write(bi, "PNG", new File("lengths.png"));
//			ImageIO.write(dyI, "PNG", new File("gradienty.png"));
//			ImageIO.write(dxI, "PNG", new File("gradientx.png"));
//			ImageIO.write(angI, "PNG", new File("angles.png"));
//		}
//		catch (IOException iox) {
//			iox.printStackTrace();
//		}
	} 
}
